package org.biouno.unochoice;

import com.alibaba.fastjson.JSONObject;
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Util;
import hudson.model.*;
import hudson.model.Queue;
import hudson.model.queue.Tasks;
import hudson.plugins.git.*;
import hudson.security.ACL;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;
import jenkins.security.FIPS140;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.transport.RemoteConfig;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.gitclient.GitURIRequirementsBuilder;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
import org.kohsuke.stapler.*;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;

import static hudson.Util.fixEmpty;
import static hudson.Util.fixEmptyAndTrim;

@ExportedBean
public class UserRemoteConfig extends AbstractDescribableImpl<UserRemoteConfig> implements Serializable {
    private static final Logger log = LoggerFactory.getLogger(UserRemoteConfig.class);
    private String name;
    private String refspec;
    private String url;
    private String credentialsId;
    private String repositoryName;
    private String uuid;
    private String branch;


    @DataBoundConstructor
    public UserRemoteConfig(String url, String name, String refspec, @CheckForNull String credentialsId, String repositoryName, String uuid, String branch) {
        this.url = fixEmptyAndTrim(url);
        this.name = fixEmpty(name);
        this.refspec = fixEmpty(refspec);
        this.credentialsId = fixEmpty(credentialsId);
        if (repositoryName.length() == 0) {
            String[] urls = this.url.split("/");
            this.repositoryName = urls[urls.length - 1].replaceAll(".git", "");
        } else {
            this.repositoryName = repositoryName;
        }
        this.uuid = UUID.randomUUID().toString();
        this.branch = branch;
        if (FIPS140.useCompliantAlgorithms() && StringUtils.isNotEmpty(this.credentialsId) && StringUtils.startsWith(this.url, "http:")) {
            throw new IllegalArgumentException("credentialsId  error");
        }
    }

    public JSONObject toJSONObject() {
        JSONObject jsonObject = new JSONObject();
        List<JSONObject> builds = new ArrayList<>();
        jsonObject.put("name", name);
        jsonObject.put("refspec", refspec);
        jsonObject.put("url", url);
        jsonObject.put("credentialsId", credentialsId);
        jsonObject.put("repositoryName", repositoryName);
        jsonObject.put("uuid", uuid);
        jsonObject.put("branch", branch);
        jsonObject.put("buildConfigs", builds);
        return jsonObject;
    }


    @Exported
    @Whitelisted
    public String getName() {
        return name;
    }

    @Exported
    @Whitelisted
    public String getRepositoryName() {
        return repositoryName;
    }

    @Exported
    @Whitelisted
    public String getUuid() {
        return uuid;
    }

    @Exported
    @Whitelisted
    public String getBranch() {
        return branch;
    }

    @Exported
    @Whitelisted
    public String getRefspec() {
        return refspec;
    }

    @Exported
    @CheckForNull
    @Whitelisted
    public String getUrl() {
        return url;
    }

    @Exported
    @Whitelisted
    @CheckForNull
    public String getCredentialsId() {
        return credentialsId;
    }




    @Override
    public String toString() {
        JSONObject jsonObject = new JSONObject();
        return jsonObject.toString();
    }

    private final static Pattern SCP_LIKE = Pattern.compile("(.*):(.*)");


    @Extension
    public static class DescriptorImpl extends Descriptor<UserRemoteConfig> {

        public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item project,
                                                     @QueryParameter String url,
                                                     @QueryParameter String credentialsId) {
            if (project == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) ||
                    project != null && !project.hasPermission(Item.EXTENDED_READ)) {
                return new StandardListBoxModel().includeCurrentValue(credentialsId);
            }
            if (project == null) {
                /* Construct a fake project, suppress the deprecation warning because the
                 * replacement for the deprecated API isn't accessible in this context. */
                @SuppressWarnings("deprecation")
                Item fakeProject = new FreeStyleProject(Jenkins.get(), "fake-" + UUID.randomUUID());
                project = fakeProject;
            }
            return new StandardListBoxModel()
                    .includeEmptyValue()
                    .includeMatchingAs(
                            project instanceof Queue.Task t
                                    ? Tasks.getAuthenticationOf(t)
                                    : ACL.SYSTEM,
                            project,
                            StandardUsernameCredentials.class,
                            GitURIRequirementsBuilder.fromUri(url).build(),
                            GitClient.CREDENTIALS_MATCHER)
                    .includeCurrentValue(credentialsId);
        }

        public ListBoxModel doFillBranchItems(@AncestorInPath Item item,
                                              @QueryParameter String url,
                                              @QueryParameter String credentialsId) {
            ListBoxModel r = new ListBoxModel();
            try {
                // Normally this permission is hidden and implied by Item.CONFIGURE, so from a view-only form you will not be able to use this check.
                // (TODO under certain circumstances being granted only USE_OWN might suffice, though this presumes a fix of JENKINS-31870.)
                if (item == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) ||
                        item != null && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
                    return r;
                }
                if (url == null || url.indexOf('$') >= 0)
                    // set by variable, can't validate
                    return r;

                // get git executable on controller
                EnvVars environment;
                Jenkins jenkins = Jenkins.get();
                if (item instanceof Job<?, ?> job) {
                    environment = job.getEnvironment(jenkins, TaskListener.NULL);
                } else {
                    Computer computer = jenkins.toComputer();
                    environment = computer == null ? new EnvVars() : computer.buildEnvironment(TaskListener.NULL);
                }

                GitClient git = Git.with(TaskListener.NULL, environment)
                        .using(GitTool.getDefaultInstallation().getGitExe())
                        .getClient();
                StandardCredentials credential = lookupCredentials(item, credentialsId, url);
                git.clearCredentials();
                git.addCredentials(url, credential);
                git.addDefaultCredentials(credential);
                try {
                    Map<String, ObjectId> branchs = git.getRemoteReferences(url, "refs/heads/*", false, false);
                    Map<String, ObjectId> release_tags = git.getRemoteReferences(url, "refs/tags/*", false, false);
                    AtomicReference<String> ci = new AtomicReference<>("");
                    AtomicReference<String> lastci = new AtomicReference<>("");
                    release_tags.keySet().forEach(branch -> {
                        String b = branch.replace("refs/tags/", "");
                        if (b.indexOf("ci_") == 0) {
                            ci.set(b);
                        } else if (b.indexOf("lastci_") == 0) {
                            lastci.set(b);
                        } else {
                            r.add(b + "<tag>", b);
                        }
                    });


                    branchs.keySet().forEach(branch -> {
                        String b = branch.replace("refs/heads/", "");
                        r.add(b + "<branch>", b);
                    });
                    log.info("ci:{},lastci:{}",ci.get(),lastci.get());
                    if (lastci.get() != "") {
                        ListBoxModel.Option elementData = new ListBoxModel.Option(lastci.get() + "<tag>", lastci.get());
                        if (ci.get().equals("")) elementData.selected = true;
                        r.add(0, elementData);
                    }
                    if (ci.get() != "") {
                        ListBoxModel.Option elementData = new ListBoxModel.Option(ci.get() + "<tag>", ci.get());
                        elementData.selected = true;
                        r.add(0, elementData);
                    }
                } catch (GitException e) {
                    e.printStackTrace();
                    System.err.println("credentialsId:" + credentialsId);
                    return r;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return r;
        }

        public FormValidation doCheckCredentialsId(@AncestorInPath Item project,
                                                   @QueryParameter String url,
                                                   @QueryParameter String value) {
            if (project == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) ||
                    project != null && !project.hasPermission(Item.EXTENDED_READ)) {
                return FormValidation.ok();
            }

            value = Util.fixEmptyAndTrim(value);
            if (value == null) {
                return FormValidation.ok();
            }

            url = Util.fixEmptyAndTrim(url);
            if (url == null)
            // not set, can't check
            {
                return FormValidation.ok();
            }

            if (url.indexOf('$') >= 0)
            // set by variable, can't check
            {
                return FormValidation.ok();
            }
            for (ListBoxModel.Option o : CredentialsProvider
                    .listCredentialsInItem(StandardUsernameCredentials.class, project, project instanceof Queue.Task t
                                    ? Tasks.getAuthenticationOf2(t)
                                    : ACL.SYSTEM2,
                            GitURIRequirementsBuilder.fromUri(url).build(),
                            GitClient.CREDENTIALS_MATCHER)) {
                if (Objects.equals(value, o.value)) {
                    // TODO check if this type of credential is acceptable to the Git client or does it merit warning
                    // NOTE: we would need to actually lookup the credential to do the check, which may require
                    // fetching the actual credential instance from a remote credentials store. Perhaps this is
                    // not required
                    return FormValidation.ok();
                }
            }
            // no credentials available, can't check
            return FormValidation.warning("Cannot find any credentials with id " + value);
        }

        @RequirePOST
        public FormValidation doCheckUrl(@AncestorInPath Item item,
                                         @QueryParameter String credentialsId,
                                         @QueryParameter String value) throws IOException, InterruptedException {


            // Normally this permission is hidden and implied by Item.CONFIGURE, so from a view-only form you will not be able to use this check.
            // (TODO under certain circumstances being granted only USE_OWN might suffice, though this presumes a fix of JENKINS-31870.)
            if (item == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER) ||
                    item != null && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
                return FormValidation.ok();
            }

            String url = Util.fixEmptyAndTrim(value);

            if (url == null || url.indexOf('$') >= 0)
                // set by variable, can't validate
                return FormValidation.ok();

            // get git executable on controller
            EnvVars environment;
            Jenkins jenkins = Jenkins.get();
            if (item instanceof Job<?, ?> job) {
                environment = job.getEnvironment(jenkins, TaskListener.NULL);
            } else {
                Computer computer = jenkins.toComputer();
                environment = computer == null ? new EnvVars() : computer.buildEnvironment(TaskListener.NULL);
            }

            GitClient git = Git.with(TaskListener.NULL, environment)
                    .using(GitTool.getDefaultInstallation().getGitExe())
                    .getClient();
            StandardCredentials credential = lookupCredentials(item, credentialsId, url);
            git.clearCredentials();
            git.addCredentials(url, credential);
            git.addDefaultCredentials(credential);

            // Should not track credentials use in any checkURL method, rather should track
            // credentials use at the point where the credential is used to perform an
            // action (like poll the repository, clone the repository, publish a change
            // to the repository).

            // attempt to connect the provided URL
            try {
                git.getHeadRev(url, "HEAD");
            } catch (GitException e) {
                e.printStackTrace();
                System.err.println("credentialsId:" + credentialsId);
                return FormValidation.error("check git url error");
            }

            return FormValidation.ok();
        }

        /**
         * A form validation logic as a method to check the specification of 'refSpec' and notify the user about
         * illegal specs before applying the project configuration
         *
         * @param name  Name of the remote repository
         * @param url   Repository URL
         * @param value value of RefSpec
         * @return FormValidation.ok() or FormValidation.error()
         * @throws IllegalArgumentException on unexpected argument error
         */
        public FormValidation doCheckRefspec(@QueryParameter String name,
                                             @QueryParameter String url,
                                             @QueryParameter String value) throws IllegalArgumentException {

            String refSpec = Util.fixEmptyAndTrim(value);

            if (refSpec == null) {
                // We fix empty field value with a default refspec, hence we send ok.
                return FormValidation.ok();
            }

            if (refSpec.contains("$")) {
                // set by variable, can't validate
                return FormValidation.ok();
            }

            Config repoConfig = new Config();

            repoConfig.setString("remote", name, "url", url);
            repoConfig.setString("remote", name, "fetch", refSpec);

            //Attempt to fetch remote repositories using the repoConfig
            try {
                RemoteConfig.getAllRemoteConfigs(repoConfig);
            } catch (Exception e) {
                return FormValidation.error("check git refspec error");
            }

            return FormValidation.ok();
        }

        public static StandardCredentials lookupCredentials(@CheckForNull Item project, String credentialId, String uri) {
            return (credentialId == null) ? null : CredentialsMatchers.firstOrNull(
                    CredentialsProvider.lookupCredentialsInItem(StandardCredentials.class, project, ACL.SYSTEM2,
                            GitURIRequirementsBuilder.fromUri(uri).build()),
                    CredentialsMatchers.withId(credentialId));
        }

        @Override
        public String getDisplayName() {
            return "";
        }
    }
}
